﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using inCom.Logs;

namespace Skybot.Logs.Broken
{
    /// <summary>
    /// 连接异常时的处理功能
    /// 1.通过 ForwardProcess 中的 SingleSendToTarget 方法 或者 BatchSendToTarget 方法中检查到了连接异常时
    /// 2.连接异常时,用用户名-目前节点 IP的方式创建文件夹 如:BJEV-192.168.1.1 9000
    /// 3.将数据写入到 文件中 文件名称为 一天N个 文件扩展名为.txt 如:2016-01-01 23,12,33,555.txt
    /// 4.一个转发记录对像二进制序列化后,使用HEX 写入一行.
    /// 5.设置为每个文件写入10万条左右,或者10分钟更换一次新文件,
    /// 6.当连接恢复后
    /// 7.读取目录下所有文件
    /// 8.将文件名,序列化为时间,并按时间升序排列
    /// 9.将文件中的数据一条条读取并且发送.
    /// 10.当前发送一个文件完成后删除一个文件.
    /// 11.除当前写入文件外其它文件都发送完成后,停止文件写入
    /// 12.将当前文件数据全部读取,一条条发送.
    /// 13.当发送完成后删除文件
    /// 14.使用缓存中的数据进行文件发送.恢复正常数据传输.
    /// </summary>
    /// <remarks>
    ///   测试信息
    //986字节包写入
    //写入1000000条用时:61345毫秒,16301.25/s
    //读取1000000条用时:132860毫秒,7526.72/s
    //----------第0次循环结束
    //写入1000000条用时:61777毫秒,16187.25/s
    //读取1000000条用时:135097毫秒,7804.18/s
    //----------第1次循环结束
    //写入1000000条用时:60583毫秒,16506.28/s
    //读取1000000条用时:146157毫秒,7525.87/s
    //-----------------------------Base64----------------------------
    //写入1000000条用时:19795毫秒,50517.81/s
    //读取1000000条用时:38683毫秒,25851.15/s
    //----------第0次循环结束
    //写入1000000条用时:19724毫秒,50699.66/s
    //读取1000000条用时:40262毫秒,24837.32/s
    //----------第1次循环结束
    //写入1000000条用时:20929毫秒,47780.59/s
    //读取1000000条用时:34108毫秒,29318.63/s
    //----------第2次循环结束
    /// </remarks>
    public class ConnBrokenSupplements
    {

        /// <summary>
        /// 当前日志的工作状态.
        /// </summary>
        public BrokenLogState BrokenLogState
        {
            get;
            set;
        }



        /// <summary>
        /// 联接中断后的日志记录目录
        /// </summary>
        public string BrokenDir { get; set; }




        /// <summary>
        /// 日志写入程序
        /// </summary>
        private  ForwardBrokegLog log = null;



        private int _lockNum = 0;

        /// <summary>
        /// 调用初始化方法 
        /// </summary>
        /// <param name="dir">用用户名-目前节点 IP的方式创建文件夹 如:BJEV-192.168.1.1 9000,注意同一个不同对像不能具有相同的目录名</param>
        public void Init(string dir)
        {

            //采用单个对像不能多次加载
            System.Threading.LazyInitializer.EnsureInitialized(ref log, () =>
            {
                System.Threading.Interlocked.Increment(ref _lockNum);
                //原子操作,只能补始化一次
                if (_lockNum < 2)
                {
                    //开始连接检查
                    BrokenDir = dir;
                    log = new ForwardBrokegLog(dir);

                }
                return log;
            });
        }
        #region 文件写入相关
        /// <summary>
        /// 文件最大记录数 默认10万条
        /// </summary>
        private int fileMaxRecordNumber = 10 * 10000;
        /// <summary>
        /// 文件最大记录数 默认10万条
        /// </summary>
        public int FileMaxRecordNumber
        {
            get { return fileMaxRecordNumber; }
            set { fileMaxRecordNumber = value; }
        }

        /// <summary>
        /// 记录文件当前记录数
        /// </summary>
        private int curRecord = 0;

        /// <summary>
        /// 当前记录数
        /// </summary>
        public int CurRecord
        {
            get { return curRecord; }
            protected set { curRecord = value; }
        }

        /// <summary>
        /// 间隔多少分钟切换一个文件
        /// </summary>
        private double minuteInterval = 10;
        /// <summary>
        /// 间隔多少分钟切换一个文件
        /// </summary>
        public double MinuteInterval
        {
            get { return minuteInterval; }
            set { minuteInterval = value; }
        }
        /// <summary>
        /// 文件最早写入时间,用于分文件大概10分钟一个文件
        /// </summary>
        public DateTime FileFirstTime { get; set; }

        /// <summary>
        /// 跟据FileFirstTime 生成的文件ToKen
        /// </summary>
        public string FileToKen { get; set; }



        /// <summary>
        /// 重新设置文件写入基本信息
        /// </summary>
        protected void ReCreateFileInfo()
        {
            System.Threading.Interlocked.Exchange(ref curRecord, 0);
            FileFirstTime = DateTime.Now;
            FileToKen = FileFirstTime.ToString(" HH,mm,ss.fff");
        }

        /// <summary>
        /// 
        /// 向文件写入数据,实际测试每秒写入1KB数据,5万条记录左右
        /// </summary>
        /// <param name="hex">二进制数据</param>
        /// 
        public bool Write(byte[] hex)
        {

            //当前正在处理最后的文件,不能写入
            if (BrokenLogState ==  BrokenLogState.LastLogFileUsedCanNotWrite)
            {
                return false;
            }

            //首次写入则初始化文件
            if (string.IsNullOrEmpty(FileToKen))
            {
                ReCreateFileInfo();
            }
            //增加1
            System.Threading.Interlocked.Increment(ref curRecord);
            //检查写入状态，判断是否需要关闭文件
            CheckWrite();

            string hexStr = Convert.ToBase64String(hex);
            // BitConverter.ToString(hex).Replace("-", string.Empty);
            //写入日志文件
            log.Write(FileToKen, hexStr, BrokenDir);
            return true;
        }

        /// <summary>
        /// 按条件生成或者关闭文件
        /// </summary>
        public void CheckWrite()
        {
            //按条件生成文件
            if (curRecord > FileMaxRecordNumber || FileFirstTime.AddMinutes(MinuteInterval) < DateTime.Now)
            {
                //关闭以前文件句柄
                log.CloseFile(FileToKen, BrokenDir);
                ReCreateFileInfo();
            }
        }


        #endregion


        #region 读取文件相关

        /// <summary>
        /// 获取当前故障记录文件数
        /// </summary>
        private int _BrokenFilesNumber = 0;
        /// <summary>
        /// 获取当前故障记录文件数
        /// </summary>
        public int BrokenFilesNumber
        {
            get
            {
                try
                {
                    System.IO.DirectoryInfo dic = new System.IO.DirectoryInfo(log.GetBaseDirectory);
                    _BrokenFilesNumber = dic.GetFiles().Length;
                }catch
                {
                }
                return _BrokenFilesNumber;
            }
         }


        /// <summary>
        /// 锁对像
        /// </summary>
        object lockobj = new object();
        /// <summary>
        /// 读取文件数据,注意此方法非线程安全,需要在一个线程中操作
        /// </summary>
        /// <param name="ReadCallBack">读取数据后的调用方法</param>
        /// <param name="filesNum">剩余的文件个数,0表示空了</param>
        /// <returns></returns>
        /// <remarks>
        /// 7.读取目录下所有文件
        /// 8.将文件名,序列化为时间,并按时间升序排列
        /// 9.将文件中的数据一条条读取并且发送.
        /// 10.当前发送一个文件完成后删除一个文件.
        /// 11.除当前写入文件外其它文件都发送完成后,停止文件写入
        /// 12.将当前文件数据全部读取,一条条发送.
        /// 13.当发送完成后删除文件
        /// 14.使用缓存中的数据进行文件发送.恢复正常数据传输.
        /// </remarks>
        public bool ReadBroken(Func<byte[],bool> ReadCallBack, out int filesNum)
        {

            System.IO.DirectoryInfo dic = new System.IO.DirectoryInfo(log.GetBaseDirectory);
            #region 处理文件夹里的文件

            filesNum = 0;
            var files = dic.GetFiles();
            filesNum = files.Length;
            //2015-06-10 11,07,11.684
            int startIndex = DateTime.Now.ToString("yyyy-MM-dd").Length;
            //获取目录下所有文件
            var tmplist = files.Select(
                    p => new
                    {
                        Token = p.Name.Substring(startIndex, p.Name.Length - p.Extension.Length - startIndex),
                        Date = DateTime.Parse(p.Name.Substring(0, p.Name.Length - p.Extension.Length).Replace(",", ":")),
                        FileInfo = p
                    });
            //正序排列
            var list = tmplist.OrderBy(p => p.Date);
            if (list.Count() == 0)
            {
                return false;
            }
            #endregion

            //开始一行行读取数据
            foreach (var item in list)
            {
                //当前正在写入的文件
                if (item.Token == FileToKen)
                {
                    //将状态改变成正在处理最后的文件不能写入
                    BrokenLogState = BrokenLogState.LastLogFileUsedCanNotWrite;
                    lock (lockobj)
                    {
                        //状态最后一个文件句柄关闭
                        log.CloseFile(FileToKen, BrokenDir);
                    }
                }
                try
                {
                    //检查文件是否可用
                    try {
                        using (var so = item.FileInfo.OpenRead()) { }
                    }
                    //打开 文件失败表示文件正在使用中
                    catch { continue; }
                    ReadFileContent(item.FileInfo, ReadCallBack);
                    filesNum = files.Length - 1;
                    // 初始化写入状态
                    if (filesNum < 1)
                    {
                        BrokenLogState =  BrokenLogState.None;
                    }
                }
                catch (Exception ex)
                {
                    inCom.Logs.LogProvider.Create().Write(EventType.Error, "读取转发恢复文件时出现异常:" + ex);
                    continue;
                }
                return true;
            }

            BrokenLogState =  BrokenLogState.None;
            return false;
        }
        /// <summary>
        /// 读取文件内容 
        /// </summary>
        /// <param name="fileInfo">文件对像</param>
        /// <param name="readCallBack">返回回调</param>
        protected void ReadFileContent(System.IO.FileInfo fileInfo, Func<byte[], bool> readCallBack)
        {
            using (System.IO.StreamReader sr = new System.IO.StreamReader(fileInfo.FullName))
            {
                try
                {
                    // "2015-06-10 11:12:07;DDD3562424EE624D98BE5D2B25828B15";
                    while (!sr.EndOfStream)
                    {
                        string str = sr.ReadLine();
                        if (str.Length > 0 && str.Contains(";"))
                        {
                            string hexstr = str.Split(';')[1];
                            if (readCallBack != null)
                            {
                                try
                                {
                                    byte[] ss = Convert.FromBase64String(hexstr);
                                    //hexstr.ToBytes();
                                    if (!readCallBack(ss))
                                    {
                                        return;
                                    }
                                }
                                catch (FormatException ex)
                                {
                                    //检查格式不正确的时候跳过数据
                                }

                            }
                        }
                    }
                }
                finally
                {
                    sr.Close();
                }
            }
            //删除文件
            fileInfo.Delete();
        }

        #endregion



    }

    /// <summary>
    /// 出现问题后的日志补传工作状态
    /// </summary>
    public enum BrokenLogState
    {
        /// <summary>
        /// 一般状态,可以写入,可以读取
        /// </summary>
        None,
        /// <summary>
        /// 正在处理最后的日志文件,当前状态为不能写入
        /// </summary>
        LastLogFileUsedCanNotWrite,

    }
}
